namespace Core
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.Caching;
    using System.Xml.XPath;
    using Base;
    using Contracts.Interfaces;
    using System.Linq;

    /// <summary>
    /// Default repository implementation class
    /// </summary>
    public sealed class DefaultRepository : IRepository
    {
        private enum ItemTypeEnum
        {
            Robot,
            Module,
            Ammunition
        }
        
        private const int DEFAULT_CACHE_DURATION = 1;

        private const string NAMESPACE_MODULE = "Core.Items.Modules.";
        private const string NAMESPACE_ROBOT = "Core.Items.Robots.";
        private const string NAMESPACE_AMMO = "Core.Items.Ammunitions.";

        private const string XPATH_MODULE_BY_NAME_FORMAT = "//module[text()=\"{0}\"]";
        private const string XPATH_ROBOT_BY_NAME_FORMAT = "//robot[text()=\"{0}\"]";
        private const string XPATH_AMMO_BY_NAME_FORMAT = "//ammunition[text()=\"{0}\"]";

        private bool _isDisposed;
        private MemoryCache _cache;
        private CacheItemPolicy _defaultCachePolicy;

        private XPathExpression _xpathModules;
        private XPathExpression _xpathRobots;
        private XPathExpression _xpathAmmos;

        /// <summary>
        /// Creates a new instance
        /// </summary>
        /// <exception cref="FileNotFoundException">An embedded resource file is missing</exception>
        public DefaultRepository(int duration = DEFAULT_CACHE_DURATION)
        {
            _cache = new MemoryCache("Repository");
            _defaultCachePolicy = new CacheItemPolicy()
                                      {
                                          Priority = CacheItemPriority.Default,
                                          SlidingExpiration = TimeSpan.FromMinutes(duration),
                                      };

            _xpathModules = XPathExpression.Compile("/modules//module");
            _xpathRobots = XPathExpression.Compile("/robots//robot");
            _xpathAmmos = XPathExpression.Compile("/ammunitions//ammunition");
        }

        public void Initialize()
        {
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            GC.SuppressFinalize(this);

            _isDisposed = true;

            if(_cache!=null)
                _cache.Dispose();
            
            _xpathModules = null;
            _xpathRobots = null;
            _xpathAmmos = null;
            _defaultCachePolicy = null;

            _cache = null;
        }
        /// <summary>
        /// Determines if the instance has been disposed
        /// </summary>
        public bool IsDisposed
        {
            get { return _isDisposed; }
        }
        /// <summary>
        /// Obtains the robots list
        /// </summary>
        public IEnumerable<IRobot> Robots
        {
            get
            {
                this.ThrowIfDisposed();
                return EnumerateAll<IRobot>(ItemTypeEnum.Robot);
            }
        }
        /// <summary>
        /// Obtains the modules list
        /// </summary>
        public IEnumerable<IModule> Modules
        {
            get
            {
                this.ThrowIfDisposed();
                return EnumerateAll<IModule>(ItemTypeEnum.Module);
            }
        }
        /// <summary>
        /// Obtains the ammunitions list
        /// </summary>
        public IEnumerable<IAmmunition> Ammunitions
        {
            get
            {
                this.ThrowIfDisposed();
                return EnumerateAll<IAmmunition>(ItemTypeEnum.Ammunition);
            }
        }
        /// <summary>
        /// Loads a new instance of an item based on its name
        /// </summary>
        /// <typeparam name="T">The type of the instance to create</typeparam>
        /// <param name="name">The name of the item</param>
        /// <returns>A new instance of T or null</returns>
        public T LoadByName<T>(string name) where T : class, ICloneable
        {
            this.ThrowIfDisposed();

            XPathNavigator navigator = null;
            if (typeof(T) == typeof(IRobot))
                navigator = CreateRobotsNavigator().SelectSingleNode(string.Format(XPATH_ROBOT_BY_NAME_FORMAT, name));
            else if (typeof(T) == typeof(IModule))
                navigator = CreateModulesNavigator().SelectSingleNode(string.Format(XPATH_MODULE_BY_NAME_FORMAT, name));
            else if (typeof(T) == typeof(IAmmunition))
                navigator = CreateAmmunitionsNavigator().SelectSingleNode(string.Format(XPATH_AMMO_BY_NAME_FORMAT,name));

            if(navigator == null || !navigator.HasAttributes)return null;
            var item = LoadByType<T>(navigator.GetAttribute("type", ""));

            return item == null
                       ? null
                       : (T) item.Clone();
        }
        /// <summary>
        /// Enumerates items using an XPath query
        /// </summary>
        /// <typeparam name="T">The type of item</typeparam>
        /// <param name="xpath">An XPath query</param>
        /// <returns>An IEnumerable of T or null</returns>
        public IEnumerable<T> LoadByPath<T>(string xpath) where T : class, ICloneable
        {
            this.ThrowIfDisposed();
            XPathNodeIterator iterator = null;
            if (typeof(T) == typeof(IRobot))
                iterator = CreateRobotsNavigator().Select(xpath);
            else if (typeof(T) == typeof(IModule))
                iterator = CreateModulesNavigator().Select(xpath);
            else if (typeof(T) == typeof(IAmmunition))
                iterator = CreateAmmunitionsNavigator().Select(xpath);

            if (iterator != null)
                while (iterator.MoveNext())
                {
                    if (iterator.Current == null || !iterator.Current.HasAttributes) continue;
                    yield return LoadByType<T>(iterator.Current.GetAttribute("type", ""));
                }
        }
        /// <summary>
        /// Determines if an item exists and if so, creates a new instance
        /// </summary>
        /// <typeparam name="T">The type of the instance</typeparam>
        /// <param name="name">The name of the item</param>
        /// <param name="instance">A new instance of T if the item exists otherwise null</param>
        /// <returns>True if the item exists otherwise false</returns>
        public bool Exists<T>(string name, out T instance) where T : class, ICloneable
        {
            this.ThrowIfDisposed();
            instance = LoadByName<T>(name);
            return instance != null;
        }
        /// <summary>
        /// Enumerates items using an XPath query
        /// </summary>
        /// <typeparam name="T">The type of item</typeparam>
        /// <param name="expression">An XPathExpression instance</param>
        /// <param name="itemType"></param>
        /// <returns>An IEnumerable of T or null</returns>
        private IEnumerable<T> LoadByPath<T>(XPathExpression expression, ItemTypeEnum itemType) where T : class, ICloneable
        {
            this.ThrowIfDisposed();
            XPathNodeIterator iterator = null;
            switch (itemType)
            {
                case ItemTypeEnum.Robot:
                    iterator = CreateRobotsNavigator().Select(expression);
                    break;
                case ItemTypeEnum.Module:
                    iterator = CreateModulesNavigator().Select(expression);
                    break;
                case ItemTypeEnum.Ammunition:
                    iterator = CreateAmmunitionsNavigator().Select(expression);
                    break;
            }

            if (iterator != null)
                while (iterator.MoveNext())
                {
                    if (iterator.Current == null || !iterator.Current.HasAttributes) continue;
                    yield return LoadByType<T>(iterator.Current.GetAttribute("type", ""));
                }
        }
        /// <summary>
        /// Loads a single instance of a type from the cache
        /// based on its type name from the cache
        /// </summary>
        /// <typeparam name="T">The type of the instance</typeparam>
        /// <param name="typeName">The name of the type</param>
        /// <returns>A single instance of T or null</returns>
        private T LoadByType<T>(string typeName) where T : class
        {
            string key = null;
            if (typeof(T) == typeof(IRobot))
                key = NAMESPACE_ROBOT +  typeName;
            else if (typeof(T) == typeof(IModule))
                key = NAMESPACE_MODULE + typeName;
            else if (typeof(T) == typeof(IAmmunition))
                key = NAMESPACE_AMMO + typeName;

            if (string.IsNullOrEmpty(key)) return null;

            return _cache.Contains(key)
                ? (T)_cache[key]
                : (T)CacheInstance(key, Activator.CreateInstance(Type.GetType(key, false, false)));
        }
        /// <summary>
        /// Adds a new instance to the internal cache
        /// </summary>
        /// <typeparam name="T">The type of the instance</typeparam>
        /// <param name="key">A unique key</param>
        /// <param name="instance">The instance to add to the cache</param>
        /// <returns>The instance cached or null if failed</returns>
        private T CacheInstance<T>(string key, T instance) where T : class
        {
            _cache.Add(new CacheItem(key, instance), _defaultCachePolicy);
            return instance;
        }
        /// <summary>
        /// Enumerates entries from the repository
        /// </summary>
        /// <typeparam name="T">The type of the entry</typeparam>
        /// <returns>An IEnumerable of T</returns>
        private IEnumerable<T> EnumerateAll<T>(ItemTypeEnum itemType) where T : class, ICloneable
        {
            switch (itemType)
            {
                case ItemTypeEnum.Robot :
                    return LoadByPath<T>(_xpathRobots,itemType);
                case ItemTypeEnum.Module :
                    return LoadByPath<T>(_xpathModules, itemType);
                case ItemTypeEnum.Ammunition :
                    return LoadByPath<T>(_xpathAmmos, itemType);
                default :
                    return null;
            }
        }

        private static XPathNavigator CreateModulesNavigator()
        {
            return new XPathDocument(
                typeof (ModuleBase).Assembly.GetManifestResourceStream("Core.Items.Modules.modules.xml"))
                .CreateNavigator();
        }

        private static XPathNavigator CreateRobotsNavigator()
        {
            return new XPathDocument(
                typeof(RobotBase).Assembly.GetManifestResourceStream("Core.Items.Robots.robots.xml"))
                .CreateNavigator();
        }

        private static XPathNavigator CreateAmmunitionsNavigator()
        {
            return new XPathDocument(
                typeof(AmmunitionBase).Assembly.GetManifestResourceStream("Core.Items.Ammunitions.ammunitions.xml"))
                .CreateNavigator();
        }
    }
}